
#include "CGIVulkanCommandBuffer.h"
#include "CGIVulkanDevice.h"
#include "CGIVulkanContext.h"
#include "CGIVulkanBarriers.h"
#include "CGIVulkanMemory.h"

namespace Alice{
    CGIVulkanCommandBuffer::CGIVulkanCommandBuffer(CGIVulkanDevice* InDevice, CGIVulkanCommandBufferPool* InCommandBufferPool, bool bInIsUploadOnly)
        :mDevice(InDevice),
        mCommandBufferPool(InCommandBufferPool),
        bIsUploadOnly(bInIsUploadOnly)
    {
        mLayoutManager=new VulkanLayoutManager(true,NULL);
        AllocMemory();
        mFence=mDevice->GetFenceManager().AllocateFence();
    }
    void CGIVulkanCommandBuffer::AllocMemory()
    {
        mCurrentViewports.clear();
        mCurrentScissors.clear();

        VkCommandBufferAllocateInfo CreateCmdBufInfo={};
        CreateCmdBufInfo.sType=VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
        CreateCmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
        CreateCmdBufInfo.commandBufferCount = 1;
        CreateCmdBufInfo.commandPool = mCommandBufferPool->GetHandle();

        vkAllocateCommandBuffers(mDevice->GetDevice(), &CreateCmdBufInfo, &mCommandBuffer);

        bNeedsDynamicStateSet = 1;
        bHasPipeline = 0;
        bHasViewport = 0;
        bHasScissor = 0;
        bHasStencilRef = 0;
        State = EState::ReadyForBegin;
    }
    void CGIVulkanCommandBuffer::RefreshFenceStatus()
    {
        if (State == EState::Submitted)
        {
        }
    }
    void CGIVulkanCommandBuffer::Begin()
    {
        {
            if(State == EState::NeedReset)
            {
                vkResetCommandBuffer(mCommandBuffer, VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
            }
            State = EState::IsInsideBegin;
        }

        VkCommandBufferBeginInfo CmdBufBeginInfo={};
        CmdBufBeginInfo.sType=VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
        CmdBufBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
        vkBeginCommandBuffer(mCommandBuffer, &CmdBufBeginInfo);
        
        bNeedsDynamicStateSet = true;
    }
    void CGIVulkanCommandBuffer::End()
    {
        vkEndCommandBuffer(GetHandle());
        State = EState::HasEnded;
    }
    inline void CGIVulkanCommandBuffer::InitializeTimings(CGIVulkanCommandListContext* InContext)
    {
    }
    CGIVulkanCommandBufferPool::CGIVulkanCommandBufferPool(CGIVulkanDevice* InDevice, CGIVulkanCommandBufferManager& InMgr)
    : mHandle(VK_NULL_HANDLE)
    , mDevice(InDevice)
    , mMgr(InMgr)
    {
    }
    void CGIVulkanCommandBufferPool::Create(uint32 QueueFamilyIndex)
    {
        VkCommandPoolCreateInfo CmdPoolInfo={};
        CmdPoolInfo.sType=VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
        CmdPoolInfo.queueFamilyIndex =  QueueFamilyIndex;
        //#todo-rco: Should we use VK_COMMAND_POOL_CREATE_TRANSIENT_BIT?
        CmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
        
        vkCreateCommandPool(mDevice->GetDevice(), &CmdPoolInfo, NULL, &mHandle);
    }
    CGIVulkanCommandBuffer* CGIVulkanCommandBufferPool::Create(bool bIsUploadOnly)
    {
        // Assumes we are inside a lock for the pool
        for (int32 Index = mFreeCmdBuffers.size() - 1; Index >= 0; --Index)
        {
            CGIVulkanCommandBuffer* CmdBuffer = mFreeCmdBuffers[Index];
            if (CmdBuffer->bIsUploadOnly == bIsUploadOnly)
            {
                mFreeCmdBuffers.erase(mFreeCmdBuffers.begin()+Index);
                CmdBuffer->AllocMemory();
                mCmdBuffers.push_back(CmdBuffer);
                return CmdBuffer;
            }
        }

        CGIVulkanCommandBuffer* CmdBuffer = new CGIVulkanCommandBuffer(mDevice, this, bIsUploadOnly);
        mCmdBuffers.push_back(CmdBuffer);
        return CmdBuffer;
    }
    void CGIVulkanCommandBufferPool::RefreshFenceStatus(CGIVulkanCommandBuffer* SkipCmdBuffer)
    {
        for (int32 Index = 0; Index < mCmdBuffers.size(); ++Index)
        {
            CGIVulkanCommandBuffer* CmdBuffer = mCmdBuffers[Index];
            if (CmdBuffer != SkipCmdBuffer)
            {
                CmdBuffer->RefreshFenceStatus();
            }
        }
    }
    CGIVulkanCommandBufferManager::CGIVulkanCommandBufferManager(CGIVulkanDevice* InDevice, CGIVulkanCommandListContext* InContext)
    : mDevice(InDevice)
    , mContext(InContext)
    , mPool(InDevice, *this)
    , mQueue(InContext->GetQueue())
    , mActiveCmdBuffer(nullptr)
    , mUploadCmdBuffer(nullptr)
    {
        mPool.Create(mQueue->GetFamilyIndex());
        mActiveCmdBuffer = mPool.Create(false);
    }
    void CGIVulkanCommandBufferManager::Init(CGIVulkanCommandListContext* InContext)
    {
        mActiveCmdBuffer->InitializeTimings(InContext);
        mActiveCmdBuffer->Begin();
    }
    CGIVulkanCommandBuffer* CGIVulkanCommandBufferManager::GetUploadCmdBuffer()
    {
        if (!mUploadCmdBuffer)
        {
            for (uint32 Index = 0; Index < mPool.mCmdBuffers.size(); ++Index)
            {
                CGIVulkanCommandBuffer* CmdBuffer = mPool.mCmdBuffers[Index];
                CmdBuffer->RefreshFenceStatus();
                if (CmdBuffer->bIsUploadOnly)
                {
                    if (CmdBuffer->State == CGIVulkanCommandBuffer::EState::ReadyForBegin || CmdBuffer->State == CGIVulkanCommandBuffer::EState::NeedReset)
                    {
                        mUploadCmdBuffer = CmdBuffer;
                        mUploadCmdBuffer->Begin();
                        return mUploadCmdBuffer;
                    }
                }
            }

            // All cmd buffers are being executed still
            mUploadCmdBuffer = mPool.Create(true);
            mUploadCmdBuffer->Begin();
        }

        return mUploadCmdBuffer;
    }
    void CGIVulkanCommandBufferManager::SubmitUploadCmdBuffer(uint32 NumSignalSemaphores, VkSemaphore* SignalSemaphores)
    {
        if (!mUploadCmdBuffer->IsSubmitted() && mUploadCmdBuffer->HasBegun())
        {
            mUploadCmdBuffer->End();
            mQueue->Submit(mUploadCmdBuffer, NumSignalSemaphores, SignalSemaphores);
        }
        mUploadCmdBuffer = nullptr;
    }
}
